
/**
 * Copyright 2009 cestum.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
package org.waveprotocol.wave.model.document.operation.util {
import mx.collections.ArrayCollection;
import mx.collections.IViewCursor;

import org.goverla.collections.ArrayList;
import org.goverla.collections.HashMap;
import org.waveprotocol.wave.model.document.operation.AttributesUpdate;
import org.waveprotocol.wave.model.util.Pair;
import org.waveprotocol.wave.model.util.Preconditions;


public class ImmutableAttributesUpdateMap implements UpdateMap {

  public var updates:ArrayList= null;
  public function changeSize():int {
    return updates.length;
  }
  
  public function getChangeKey(i:int):String {
    return (AttributeUpdate(updates.getItemAt(i))).name;
  }

  
  public function getOldValue(i:int):String {
    return (AttributeUpdate(updates.getItemAt(i))).oldValue;
  }

  
  public function getNewValue(i:int):String {
    return (AttributeUpdate(updates.getItemAt(i))).newValue;
  }

  public function ImmutableAttributesUpdateMap(al:ArrayList) {
	  if (al == null)
		  updates = new ArrayList();
	  else
		  updates = al;
  }
  
//  public static ImmutableUpdateMap of(Map updates) {
//	    return new ImmutableUpdateMap(of(tripletsFromMap(updates)));
//  }
  
  
  public static function of(triples:Array):ArrayList {
    if (triples.length % 3!= 0) {
      throw new Error("Triples must come in groups of three");
    }

    var accu:ArrayList= new ArrayList();
    for (var i:int= 0; i < triples.length; i += 3) {
      Preconditions.checkNotNull(triples[i], "Null key");
      accu.addItem(new AttributeUpdate(triples[i], triples[i + 1], triples[i + 2]));
    }

//    Collections.sort(accu, comparator);

    for (var i:int= 1; i < accu.length; i++) {
      var x:int= AttributeUpdate(accu.getItemAt(i - 1)).compareTo(AttributeUpdate(accu.getItemAt(i)));
      if (x == 0) {
        throw new Error("Duplicate key: " + (AttributeUpdate(accu.getItemAt(i))).name);
      }
      org.waveprotocol.wave.model.util.Assert.assert(x < 0);
    }
    return accu;
  }
  

  private static function tripletsFromMap(updates:HashMap):Array {
    var triplets:Array= [];
    var i:int= 0;
//    Map<String, Pair> up = updates;
    for (var key:String in updates.keys) {
      triplets[i++] = key;
      triplets[i++] = String(Pair(updates.getItem(key)).getFirst());
      triplets[i++] = String(Pair(updates.getItem(key)).getSecond());
    }
    return triplets;
  }

//  protected ImmutableUpdateMap(List<AttributeUpdate> updates) {
//    this.updates = updates;
//  }

  public function exclude(names:ArrayCollection):AttributesUpdate {
		var newAttributes:ArrayList= new ArrayList();
//		ArrayList<AttributeUpdate> temp = updates;
		for (var c:Object in updates) {
			var update:AttributeUpdate =AttributeUpdate(c);
			if (!names.contains(update.name)) {
				newAttributes.addItem(update);
			}
		}
		return createFromList(newAttributes);
	}

//  protected static final Comparator<AttributeUpdate> comparator = new Comparator<AttributeUpdate>() {
//    
//    public function compare(a:AttributeUpdate, b:AttributeUpdate):int {
//      return a.name.compareTo(b.name);
//    }
//  };

  public function composeWith(mutation:AttributesUpdate):AttributesUpdate {
    var newAttributes:ArrayList= new ArrayList();
    var iterator:IViewCursor = updates.createCursor();
    var nextAttribute:AttributeUpdate= AttributeUpdate(iterator.current);
    iterator.moveNext();
    // TODO: Have a slow path when the cast would fail.
//    ArrayList<AttributeUpdate> mutationAttributes = ;
	var size:int = (ImmutableAttributesUpdateMap(mutation)).updates.length;
    myloop: for(var i:int=0; i< size; i++) {
      var attribute:AttributeUpdate = AttributeUpdate((ImmutableAttributesUpdateMap(mutation)).updates.getItemAt(i));
      while (nextAttribute != null) {
        var comparison:int= attribute.compareTo(nextAttribute);
        if (comparison < 0) {
          break;
        } else if (comparison > 0) {
          newAttributes.addItem(nextAttribute);
          nextAttribute = AttributeUpdate(iterator.current);
          iterator.moveNext();
        } else {
          if (!areEqual(nextAttribute.newValue, attribute.oldValue)) {
            throw new Error("Mismatched old value: attempt to update " +
                nextAttribute + " with " + attribute);
          }
          newAttributes.addItem(new AttributeUpdate(attribute.name, nextAttribute.oldValue,
              attribute.newValue));
          nextAttribute = AttributeUpdate(iterator.current);
          iterator.moveNext();
          continue myloop;
        }
      }
      newAttributes.addItem(attribute);
    }
    if (nextAttribute != null) {
      newAttributes.addItem(nextAttribute);
      while (!iterator.afterLast) {
        newAttributes.addItem(iterator.current);
        iterator.moveNext();
      }
    }
    return createFromList(newAttributes);
  }

  protected function createFromList(attributes:ArrayList):AttributesUpdate { return null;} ;

  // TODO: Is there a utility method for this somewhere?
  private function areEqual(a:Object, b:Object):Boolean {
    return (a == null) ? b == null : a.equals(b);
  }

  
  public function toString():String {
    return "Updates: " + updates;
  }

  public static function checkUpdatesSorted(updates:ArrayList):void {
    var previous:AttributeUpdate= null;
//    ArrayList<AttributeUpdate> temp = updates;
    for (var o:Object in updates) {
		var u:AttributeUpdate = AttributeUpdate(o);
      Preconditions.checkNotNull(u, "Null attribute update");
      org.waveprotocol.wave.model.util.Assert.assert(u.name != null);
      if (previous != null) {
        Preconditions.checkArgument(previous.name.localeCompare(u.name) < 0,
            "Attribute keys not strictly monotonic: " + previous.name + ", " + u.name);
      }
      previous = u;
    }
  }

}
}